using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Text;
using SimpleJSON;
using UnityEngine;

namespace Gj
{
    public class HttpTools
    {
        private static HttpTools instance;
        private static readonly object _lock = new();
        private static readonly CookieJar cookieJar = new();

        private HttpTools()
        {
        }

        public static HttpTools GetInstance()
        {
            if (instance == null)
                lock (_lock)
                {
                    if (instance == null) instance = new HttpTools();
                }

            return instance;
        }

        public IEnumerator Get(string url, Dictionary<string, string> data, Dictionary<string, string> headers,
            Action<bool, WWW> CB)
        {
            var etor = data.GetEnumerator();
            url += "?";
            while (etor.MoveNext()) url += etor.Current.Key + "=" + WWW.EscapeURL(etor.Current.Value) + "&";

            url = url.Remove(url.Length - 1);
            // Debug.Log($"Get: {url}");
            return IESend(url, null, headers, CB);
        }

        public IEnumerator Post(string url, Dictionary<string, string> data, Dictionary<string, string> headers,
            Action<bool, WWW> CB)
        {
            var wf = new WWWForm();
            var etor = data.GetEnumerator();
            while (etor.MoveNext()) wf.AddField(etor.Current.Key, etor.Current.Value);

            return Post(url, wf, headers, CB);
        }

        public IEnumerator Post(string url, string data, Dictionary<string, string> headers, Action<bool, WWW> CB)
        {
            var postData = Encoding.UTF8.GetBytes(data);
            return Post(url, postData, headers, CB);
        }

        public IEnumerator Post(string url, byte[] data, Dictionary<string, string> headers, Action<bool, WWW> CB)
        {
            return IESend(url, data, headers, CB);
        }

        public IEnumerator Post(string url, WWWForm form, Dictionary<string, string> headers, Action<bool, WWW> CB)
        {
            var etorHeader = form.headers.GetEnumerator();
            while (etorHeader.MoveNext()) headers.Add(etorHeader.Current.Key, etorHeader.Current.Value);

            return IESend(url, form.data, headers, CB);
        }

        public IEnumerator WrapGet(string url, Dictionary<string, string> data, Action<bool, JSONNode, string> CB)
        {
            var headers = new Dictionary<string, string>();
            headers.Add("Content-type", "application/json");
            return Get(url, data, headers, (success, www) =>
            {
                if (!success) CB(false, null, www.error);

                var o = JSONNode.Parse(www.text);
                if (o["status"].AsInt != 200)
                    CB(false, null, o["message"]);
                else
                    CB(true, o["result"].AsObject, "success");
            });
        }

        public IEnumerator WrapPost(string url, Dictionary<string, string> data, Action<bool, JSONNode, string> CB)
        {
            var headers = new Dictionary<string, string>();
            headers.Add("Content-type", "application/json");
            return Post(url, data, headers, (success, www) =>
            {
                if (!success) CB(false, null, www.error);

                var o = JSONNode.Parse(www.text);
                if (o["status"].AsInt != 200)
                    CB(false, null, o["message"]);
                else
                    CB(true, o["result"].AsObject, "success");
            });
        }

        public IEnumerator WrapPostForm(string url, WWWForm form, Action<bool, JSONNode, string> CB)
        {
            var headers = new Dictionary<string, string>();
            return Post(url, form, headers, (success, www) =>
            {
                if (!success) CB(false, null, www.error);

                var o = JSONNode.Parse(www.text);
                if (o["status"].AsInt != 200)
                    CB(false, null, o["message"]);
                else
                    CB(true, o["result"].AsObject, "success");
            });
        }

        private IEnumerator IESend(string url, byte[] data, Dictionary<string, string> headers, Action<bool, WWW> CB)
        {
            headers.Add("Cookie", cookieJar.Generate(url));
            var www = new WWW(url, data, headers);
            while (!www.isDone)
            {
                // Debug.LogError(www.progress);
                yield return www;
            }

            if (www.error != null)
            {
                Debug.LogError(www.error);
            }
            else
            {
                cookieJar.Parse(www);
            }

            if (CB != null)
            {
                if (www.error != null)
                    CB(false, www);
                else
                    CB(true, www);
            }
        }

        public class CookieJar
        {
            private readonly CookieContainer cc = new();

            public void Set(string name, string value)
            {
                var c = new Cookie(name, value);
                cc.Add(c);
            }

            public void Set(string name, string value, string path, string domain)
            {
                var c = new Cookie(name, value, path, domain);
                cc.Add(c);
            }

            public void Parse(WWW www)
            {
                if (!www.responseHeaders.ContainsKey("SET-COOKIE")) return;

                var rhsPropInfo = typeof(WWW).GetProperty("responseHeadersString",
                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                if (rhsPropInfo == null) return;
                var headersString = rhsPropInfo.GetValue(www, null) as string;
                if (headersString == null) return;
                var uri = new Uri(www.url);
                var lines = headersString.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var l in lines)
                {
                    var colIdx = l.IndexOf(':');
                    if (colIdx < 1) continue;
                    var headerType = l.Substring(0, colIdx).Trim();
                    if (headerType.ToUpperInvariant() != "SET-COOKIE") continue;
                    var headerVal = l.Substring(colIdx + 1).Trim();
                    var items = headerVal.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
                    var info = items[0].Split(new[] { "=" }, StringSplitOptions.RemoveEmptyEntries);
                    if (info.Length != 2) continue;
                    Set(info[0], info[1], "/", uri.Host);
                }
            }

            public string Generate(string url)
            {
                var cookie = cc.GetCookieHeader(new Uri(url));

                return cookie;
            }
        }
    }
}